// rdiffdelta.cpp: 定义控制台应用程序的入口点。
//

//#include "stdafx.h"
#include <stdio.h>
#include <string>
#include<fstream>
#include <dirent.h>
#include <sys/stat.h>
#include <stdint.h>
#include "protobuf\blake_delta.pb.h"
#include <list>
#include <array>
#include "sig.h"
#include "debug.h"
extern "C" {
#include "md5.h"
#include "check_sum.h"
}
#include <map>

#undef min
#undef max
#include "filecache.h"

using namespace std;

typedef std::array<uint8_t, MAX_STRONG_SUM_LENGTH> strong_sum_array_t;
typedef std::map<strong_sum_array_t,std::list<block_info_t>>  SM;
typedef std::map<weak_sum_t, SM> DB;

struct  diff_job_t
{
  DB * db;
  FILE* fp;
  Delta * delta; 
  unsigned char data[BLOCK_LENGTH];
  int data_len;
  int off_set;
  int size;
  int file_NO;
  int add_set;
};

void deal_data(diff_job_t * job);

string  md5_file(char* filepath) {
#define READ_DATA_SIZE 1024
  unsigned char digest[16] = { 0 };
  unsigned char data[READ_DATA_SIZE];
  char md5_str[33];
  MD5_CTX context;
  MD5Init(&context);
  //if (-1 != stat(filepath, &st)) {
  FILE *fp;
  int ret = 0;
  ret = fopen_s(&fp, filepath, "rb");
  if (ret != 0)
  {
    perror("fopen");
    PDEBUG("fopen :  %s\n", filepath);
    exit(-1);
  }

  while (1) {
    ret = fread(data, 1, READ_DATA_SIZE, fp);
    if (-1 == ret) {
      perror("fread");
      fclose(fp);
      exit(-1);
    }

    MD5Update(&context, data, ret);
    if (ret == 0 || ret < READ_DATA_SIZE) {
      break;
    }
  }
  fclose(fp);

  MD5Final(&context, digest);
  for (int i = 0; i < 16; i++)
  {
    snprintf(md5_str + i * 2, 2 + 1, "%02x", digest[i]);
  }
  md5_str[32] = '\0'; // add end
  return  md5_str;
}

void ReadDir(string PATH, Delta* delta, bool isold);

void ReadDir(string PATH, Delta* delta, bool isold) {

  char infile[MAX_PATH];
  struct dirent *ptr;
  DIR *dir;
  dir = opendir(PATH.c_str());
  if (dir == nullptr) {
    perror("opendir");
    exit(-1);
  }
  while ((ptr = readdir(dir)) != NULL) {
    if (ptr->d_name[0] == '.')
      continue;
    if (ptr->d_type == DT_DIR) {
      string s = PATH + "\\" + ptr->d_name;
      ReadDir(s, delta, isold);
    } else {
      sprintf_s(infile, "%s\\%s", PATH.c_str(), ptr->d_name);
      PDEBUG("%s\n", infile);
      PDEBUG("%s\n", (md5_file(infile)).c_str()); 
      if (isold) { 
        File * file;
        file = delta->add_list_file_old();
        file->set_filename(infile);
        file->set_blake2((md5_file(infile)).c_str());
      } else {
        Patch * patch; 
        patch = delta->add_list_file_patch();
        File * file = patch->mutable_new_file();
        file->set_filename(infile);
        file->set_blake2((md5_file(infile)).c_str());
      }
     
    }
  }
  closedir(dir);
}

bool  ensure_path(char * filename) {
  int i = 1;
  if (filename == NULL)
    return false;
  char * filepath = _strdup(filename);
  int len = strlen(filepath);

  while (filepath[len - i] != '\\' && filepath[len - i] != '/') {
    i++;
    if (i >= len)
      return false;
  }
  filepath[len - i + 1] = '\0';
  len = strlen(filepath);
  for (i = 0; i< len; i++) {
    if (filepath[i] == '\\' || filepath[i] == '/') {
      if (i == 0)
        continue;
      char d = filepath[i];
      filepath[i] = '\0';
      int ret = ACCESS(filepath, 0);
      if (ret != 0) {
        ret = MKDIR(filepath);
        if (ret != 0) {
          return false;
        }
      }
      filepath[i] = d;
    }
  }
  free(filepath);
  return true;
}

string insert(string p, char * path) {
  p = p.insert(0, path);
  return p;
}

string erase(string p, char * path) {
  p = p.erase(0, strlen(path));
  return p;
}

string strong_sum_to_string(strong_sum_t sum) {
  char blake2_str[65];
  for (int j = 0; j < 32; j++) {
    snprintf(blake2_str + j * 2, 2 + 1, "%02x", sum[j]);
  }
  blake2_str[64] = '\0';
  string s(blake2_str);
  return s;
}

strong_sum_array_t to_strong_sum_array_t(const strong_sum_t &s) {
  strong_sum_array_t ret;
  for (int i = 0; i < MAX_STRONG_SUM_LENGTH; i++) {
    ret[i] = s[i]; 
  }
  return ret;
}

void remove_path(Delta *delta, char * OLD, char * NEW) {
  for (int i = 0; i < delta->list_file_old_size(); i++) {
    delta->mutable_list_file_old(i)->set_filename(erase(delta->mutable_list_file_old(i)->filename(), OLD));
  }
  for (int i = 0; i < delta->list_file_patch_size(); i++) {
    delta->mutable_list_file_patch(i)->mutable_new_file()->set_filename(erase(delta->mutable_list_file_patch(i)->mutable_new_file()->filename(), NEW));
  }
}

bool check_strong_sum(const strong_sum_t & s_sum, block_info_t block, Delta* delta) {
  strong_sum_t cs_sum;
  unsigned char data[BLOCK_LENGTH];
  if (-1 == ACCESS(delta->mutable_list_file_old(block.file_NO)->filename().c_str(), 0)) {
    perror("check strong sum   old file");
    PDEBUG("file  %s can't access!\n", delta->mutable_list_file_old(block.file_NO)->filename().c_str());
    exit(-1);
  }
  FILE *fp;
  int ret = 0;
  ret = fopen_s(&fp, delta->mutable_list_file_old(block.file_NO)->filename().c_str(), "rb");
  if (ret != 0) {
    perror("check strong sum   fopen");
    PDEBUG("file  %s can't open!\n", delta->mutable_list_file_old(block.file_NO)->filename().c_str());
    exit(-1);
  }
  int off_set = block.offect;
  fseek(fp,off_set,SEEK_SET);
  ret = fread(data, 1, block.length, fp);
  if (-1 == ret) {
    perror("check strong sum  fread");
    PDEBUG("read file %s \n", delta->mutable_list_file_old(block.file_NO)->filename().c_str());
    fclose(fp);
    exit(-1);
  }
  fclose(fp);
  if (ret != block.length)
    PDEBUG("check strong  sum  fread not match\n");
  blake2_sum(data, ret, &cs_sum);
  return (to_strong_sum_array_t(s_sum) == to_strong_sum_array_t(cs_sum));
}

int block_match_length(Delta * delta,block_info_t block,FILE * fp,int read_len,int file_no) {
  int pos = 0;
  struct stat st;
  //unsigned char data[BLOCK_LENGTH];
  if (-1 != stat(delta->mutable_list_file_old(block.file_NO)->filename().c_str(), &st)) {
    FILE *fs;
    int ret = 0;
    ret = fopen_s(&fs, delta->mutable_list_file_old(block.file_NO)->filename().c_str(), "rb");
    if (ret != 0) {
      perror("block_match_length  fopen");
      PDEBUG("file  %s can't open!\n", delta->mutable_list_file_old(block.file_NO)->filename().c_str());
      exit(-1);
    }
    
    read_len = read_len <= st.st_size - block.offect ? read_len : st.st_size - block.offect;
    unsigned char * datas = (unsigned char *) malloc(read_len-BLOCK_LENGTH); 
     if(fseek(fs, block.offect + BLOCK_LENGTH, SEEK_SET)!=0)
       perror("fseek  block_matck_length error");
      ret = fread(datas, 1, read_len - BLOCK_LENGTH, fs);
      if (-1 == ret) {
        perror("block_match_length fread");
        PDEBUG("read file %s \n", delta->mutable_list_file_old(block.file_NO)->filename().c_str());
        free(datas);
        fclose(fs);
        exit(-1);
      }

      if (ret == 0) {
        free(datas);
        fclose(fs);
        return pos;
      }

      fclose(fs);

      unsigned char * datap = (unsigned char *)malloc(read_len-BLOCK_LENGTH);
      ret = fread(datap, 1, read_len - BLOCK_LENGTH, fp);
      if (-1 == ret) {
        perror("block_match_length  fread  fp  ");
        PDEBUG("read file %s \n", delta->mutable_list_file_patch(file_no)->mutable_new_file()->filename().c_str());
        free(datap);
        free(datas);
        fclose(fp);
        exit(-1);
      }

      if (ret == 0) {
        free(datas);
        free(datap);
        return pos;
      }
      
      while (pos < read_len - BLOCK_LENGTH) {
        if (datas[pos] == datap[pos]) {
          pos++;
        } else
        {
          free(datas);
          free(datap);
          return pos;
        }
      }
      free(datas);
      free(datap);
  } else {
    perror("match_block_length  stat");
    PDEBUG("match_block_length  file %s ! \n", delta->mutable_list_file_old(block.file_NO)->filename().c_str());
    fclose(fp);
    exit(-1);
  }
  return pos;
  }




void add_operate(diff_job_t * job) {
  PDEBUG("xxxxxxx   add  add  %d \n",job->add_set);
  char * datap = (char *)malloc(job->add_set);
  if(fseek(job->fp, job->off_set, SEEK_SET)!=0)
    perror("fseek  add_operate");
  int ret = fread(datap, 1, job->add_set, job->fp);
  if (ret == -1) {
    perror("  fread");
    PDEBUG("read file %s \n", job->delta->mutable_list_file_patch(job->file_NO)->mutable_new_file()->filename().c_str());
    fclose(job->fp);
    free(datap);
    exit(-1);
  }
  Operate * op = job->delta->mutable_list_file_patch(job->file_NO)->add_list_opr();
  op->set_copy(false);
  op->set_code(datap,job->add_set);
  op->set_length(job->add_set);
  free(datap);
  job->off_set += job->add_set;
  job->add_set = 0;
  PDEBUG("xxxxxxx   add  add done offset = %d  space used = %d \n", job->off_set,job->delta->SpaceUsedLong());
}
/*
void roll_file(diff_job_t * job) {
  
  if (job->add_set + job->off_set < job->size) {
    job->add_set += 1;
    fseek(job->fp, job->off_set + job->add_set, SEEK_SET);
    int ret = fread(job->data, 1, BLOCK_LENGTH, job->fp);
    if (ret == -1) {
      perror("  fread");
      PDEBUG("read file %s \n", job->delta->mutable_list_file_patch(job->file_NO)->mutable_new_file()->filename().c_str());
      fclose(job->fp);
      exit(-1);
    }
    printf("roll_file    deal_data\n");
    job->data_len = ret;
    deal_data(job);
  } else {
    printf("roll_file   over  size\n ");
    add_operate(job);
  }
}
*/

void find_weak_sum(diff_job_t *job,weak_sum_t  w_sum) {
  //job->add_set += job->data_len;
  int len = job->data_len;
  Rollsum sum;
  RollsumInit(&sum);
  RollsumUpdate(&sum, job->data, job->data_len); 
  if (w_sum == RollsumDigest(&sum)) {
    DB::iterator it;
    SM::iterator sm_it;
    while (1) {

      if (job->off_set + job->add_set+len == job->size) {
        job->add_set += len;
        add_operate(job);
        PDEBUG("run x\n");
        break;
      }
      job->data_len = fread(job->data, 1, 1, job->fp);
      if (job->data_len != 1) {
        perror("find_weak_sum    roll_sum ");
        PDEBUG("find_weak_sum  roll_sum error \n");
        fclose(job->fp);
        exit(-1);
      }
      job->add_set++;
      RollsumUpdate(&sum, job->data, job->data_len);
      it = job->db->find(RollsumDigest(&sum));
      if (it == job->db->end()) {
        // not find 
        continue;
      } else {
        PDEBUG("break xxx\n");
        break;
      }
    }
  } else {
    PDEBUG("find_weak_sum error\n");
    fclose(job->fp);
    exit(-1);
  }
}

void deal_data(diff_job_t * job) {
  weak_sum_t w_sum = weak_sum(job->data, job->data_len);
  
  strong_sum_t s_sum;
  blake2_sum(job->data,job->data_len,&s_sum);
  strong_sum_array_t array_s_sum = to_strong_sum_array_t(s_sum);
 
  DB::iterator it = job->db->find(w_sum);
  if (it == job->db->end()) {
    // not find 
    find_weak_sum(job, w_sum);
    
  } else
  {
    //find weak_sum
    SM::iterator sm_it = it->second.find(array_s_sum);
    if (sm_it == it->second.end()) {
      //not find stong_sum
      job->add_set++;
      if (job->off_set + job->add_set == job->size) {
        add_operate(job);
        PDEBUG("run xxx\n");
      }
    } else {
      //find_strong_sum
      //printf("xxxx  find_strong_sum  %d\n",job->add_set);

      if (job->add_set != 0) {
        add_operate(job);
        PDEBUG("run xxxxx\n");
      }

      if (job->data_len < BLOCK_LENGTH) {
        //add copy
        PDEBUG("add copy xxxxx\n");
        if (check_strong_sum(s_sum, sm_it->second.back(), job->delta)) {
          printf("XXXXXXXXXXXXXXXXXXX   add copy %d \n",job->data_len);
          Operate * op = job->delta->mutable_list_file_patch(job->file_NO)->add_list_opr();
          op->set_copy(true);
          op->set_length(sm_it->second.back().length);
          op->set_offset(sm_it->second.back().offect);
          op->set_code(to_string(sm_it->second.back().file_NO));
          job->off_set += job->data_len;
        } else {
          PDEBUG("XXXXXXXXXXXXXXXXXXX   not match copy\n");
          fclose(job->fp);
          exit(-1);
        }
        PDEBUG("add copy done xxxxx\n");
        return;
      } else {  //match best
        PDEBUG("match best xxxxx\n");
        int count = 0;
        int match_length = 0;
        for (int i = 0; i < sm_it->second.size(); i++) {
          list<block_info_t>::iterator iter = sm_it->second.begin();
          advance(iter, i);
          block_info_t block = *iter;
          fseek(job->fp,job->off_set+BLOCK_LENGTH,SEEK_SET);
          PDEBUG("match %d of %d xxxxx\n",i,sm_it->second.size());
          int pos =  block_match_length(job->delta,block,job->fp,job->size - job->off_set,job->file_NO);
          PDEBUG("match length %d  ?  %d xxxxx\n",match_length,pos);
          if (pos > match_length) {
            match_length = pos;
            count = i;
          }
        }
        PDEBUG("match  xxxxx\n");
        //add 
        if (check_strong_sum(s_sum, sm_it->second.back(), job->delta)) {
          PDEBUG("XXXXXXXXXXXXXXXXXXX  add copy match_leng %d \n",match_length+BLOCK_LENGTH);
          Operate * op = job->delta->mutable_list_file_patch(job->file_NO)->add_list_opr();
          list<block_info_t>::iterator iter = sm_it->second.begin();
          advance(iter, count);
          block_info_t block = *iter;
          op->set_copy(true);
          op->set_length(match_length + BLOCK_LENGTH);
          op->set_offset(block.offect);
          op->set_code(to_string(block.file_NO));
          job->off_set += match_length + BLOCK_LENGTH;
        } else {
          PDEBUG("XXXXXXXXXXXXXXXXXXX   not match copy\n");
          fclose(job->fp);
          exit(-1);
        }
        PDEBUG("match add done xxxxx\n");
      }
    }
  }
}

void delta_file2(diff_job_t * job) {
  std::string err;
  std::string filename = job->delta->list_file_patch(job->file_NO).new_file().filename();
  std::string filename_old;
  int copy_file_NO;
  off_t copy_offset;
  FileCache<> fc_new, fc_old;
  DataCache add_data;
  int ret;
  bool init_roll_sum = true;
  Rollsum roll_sum;
  weak_sum_t weak_sum;
  strong_sum_t strong_sum;
  const uint8_t *data, *data_old;
  size_t len;
  auto do_add = [&job, &add_data](void) {
    size_t len;
    auto data = add_data.get(len);
    if (len > 0) {
      Operate * op = job->delta->mutable_list_file_patch(job->file_NO)->add_list_opr();
      op->set_copy(false);
      op->set_code(data.get(), len);
      op->set_length(len);
      PDEBUG("OP: add %zu\n", len);
    }
  };
  if (fc_new.open(filename) < 0) {
    err = "open new file: " + filename;
    goto FAIL;
  }
SCAN:
  for (;;) {
    ret = fc_new.read(BLOCK_LENGTH, data, len);
    if (ret < 0) {
      err = "read new file: " + filename;
      goto FAIL;
    } else if (len < BLOCK_LENGTH) {
      add_data.prestore(data + add_data.unconfirmed(), len - add_data.unconfirmed());
      add_data.confirm(add_data.unconfirmed());
      do_add();
      goto FINISH;
    }
    if (init_roll_sum) {
      init_roll_sum = false;
      RollsumInit(&roll_sum);
      RollsumUpdate(&roll_sum, data, BLOCK_LENGTH);
      weak_sum = RollsumDigest(&roll_sum);
      RollsumRollout(&roll_sum, data[0]);
    } else {
      RollsumRollin(&roll_sum, data[BLOCK_LENGTH - 1]);
      weak_sum = RollsumDigest(&roll_sum);
      RollsumRollout(&roll_sum, data[0]);
    }
    bool copy = false;
    auto it1 = job->db->find(weak_sum);
    if (it1 != job->db->end()) {
      blake2_sum(data, BLOCK_LENGTH, &strong_sum);
      auto it2 = it1->second.find(to_strong_sum_array_t(strong_sum));
      if (it2 != it1->second.end()) {
        for (auto &m : it2->second) {
          filename_old = job->delta->list_file_old(m.file_NO).filename();
          if (fc_old.open(filename_old) < 0) {
            err = "open old file: " + filename_old;
            goto FAIL;
          }
          if (fc_old.seek(m.offect) < 0) {
            err = "seek old file: " + filename_old + ", " + std::to_string(m.offect);
            goto FAIL;
          }
          ret = fc_old.read(BLOCK_LENGTH, data_old, len);
          if (ret < 0) {
            err = "read old file: " + filename_old;
            goto FAIL;
          } else if (len < BLOCK_LENGTH) {
            err = "read old file (data missing): " + filename_old;
            goto FAIL;
          }
          copy = true;
          for (int i = 0; i < BLOCK_LENGTH; i++) {
            if (data[i] != data_old[i]) {
              copy = false;
              break;
            }
          }
          if (copy) {
            copy_file_NO = m.file_NO;
            copy_offset = m.offect;
            break;
          }
        }
      }
    }
    if (copy) {
      do_add();
      init_roll_sum = true;
      goto COPY;
    } else {
      ret = add_data.confirm(1);
      if (ret == 0) {
        add_data.prestore(data, BLOCK_LENGTH);
        add_data.confirm(1);
      }
      fc_new.drop(1);
    }
  }
COPY:
  {
    size_t copy_len = BLOCK_LENGTH, new_len, old_len, min_len;
    fc_new.drop(BLOCK_LENGTH);
    fc_old.drop(BLOCK_LENGTH);
    for (;;) {
      ret = fc_new.read(BLOCK_LENGTH, data, new_len);
      if (ret < 0) {
        err = "read new file: " + filename;
        goto FAIL;
      }
      ret = fc_old.read(BLOCK_LENGTH, data_old, old_len);
      if (ret < 0) {
        err = "read old file: " + filename_old;
        goto FAIL;
      }
      min_len = std::min(new_len, old_len);
      for (int i = 0; i < min_len; i++) {
        if (data[i] != data_old[i]) {
          min_len = i;
          break;
        }
      }
      copy_len += min_len;
      fc_new.drop(min_len);
      fc_old.drop(min_len);
      if (min_len < BLOCK_LENGTH) {
        break;
      }
    }
    Operate * op = job->delta->mutable_list_file_patch(job->file_NO)->add_list_opr();
    op->set_copy(true);
    op->set_code(to_string(copy_file_NO));
    op->set_offset(copy_offset);
    op->set_length(copy_len);
    PDEBUG("OP: copy %zu\n", copy_len);
    goto SCAN;
  }
FINISH:
  return;
FAIL:
  perror(err.c_str());
  PDEBUG("%s\n", err.c_str());
  exit(-1);
}

void delta_file(diff_job_t * job) { 
  struct stat st;
  if (-1 != stat(job->delta->mutable_list_file_patch(job->file_NO)->mutable_new_file()->filename().c_str(), &st)) {
    FILE *fp;
    int ret = 0;
    ret = fopen_s(&fp, job->delta->mutable_list_file_patch(job->file_NO)->mutable_new_file()->filename().c_str(), "rb");
    if (ret != 0) {
      perror("fopen");
      PDEBUG("file  %s can't open!\n", job->delta->mutable_list_file_patch(job->file_NO)->mutable_new_file()->filename().c_str());
      exit(-1);
    }
   
    job->fp = fp;
    job->off_set = 0;
    job->add_set = 0;
    job->size = st.st_size;

    while (job->off_set < job->size) {
      PDEBUG("%s off_set %d  add_set %d  file_size %d  spaceUsed %d\n", job->delta->mutable_list_file_patch(job->file_NO)->mutable_new_file()->filename().c_str(), job->off_set, 
        job->add_set, job->size,job->delta->SpaceUsedLong());
      if (fseek(fp, job->off_set + job->add_set, SEEK_SET) != 0)
      {
        perror("fseek");
        PDEBUG(" off_set %d  add_set %d  file_size %d\n", job->off_set,job->add_set,job->size);
        fclose(fp);
        exit(-1);
      };
      ret = fread(job->data, 1, BLOCK_LENGTH, fp);
      if (-1 == ret) {
        perror("fread");
        PDEBUG("read file %s \n", job->delta->mutable_list_file_patch(job->file_NO)->mutable_new_file()->filename().c_str());
        fclose(fp);
        exit(-1);
      }

      if (ret == 0)
        break;

      job->data_len = ret;
      deal_data(job);

      
    }
    fclose(fp);
  } else {
    perror("file stat");
    PDEBUG("delta file %s ! \n", job->delta->mutable_list_file_patch(job->file_NO)->mutable_new_file()->filename().c_str());
    fclose(job->fp);
    exit(-1);
  }
}



void diff(Delta * delta, char *DELTA) {
   DB db;
  unsigned char data[BLOCK_LENGTH];
  //read
  for (int i = 0; i < delta->list_file_old_size(); i++) {
    if ( -1 == ACCESS(delta->mutable_list_file_old(i)->filename().c_str(),0)) {
      perror("old file");
      PDEBUG("file  %s can't access!\n",delta->mutable_list_file_old(i)->filename().c_str());
      exit(-1);
    }
    FILE *fp;
    int ret = 0;
    ret = fopen_s(&fp, delta->mutable_list_file_old(i)->filename().c_str(), "rb");
    if (ret != 0) {
      perror("fopen");
      PDEBUG("file  %s can't open!\n", delta->mutable_list_file_old(i)->filename().c_str());
      exit(-1);
    }
    int off_set = 0;
    while (1) {
      ret = fread(data, 1, BLOCK_LENGTH, fp);
      if (-1 == ret) {
        perror("fread");
        PDEBUG("read file %s \n", delta->mutable_list_file_old(i)->filename().c_str());
        fclose(fp);
        exit(-1);
      }

      if (ret < BLOCK_LENGTH)
        break;
      weak_sum_t w_sum = weak_sum(data, ret);
      strong_sum_t s_sum; 
      blake2_sum(data, ret, &s_sum);
      strong_sum_array_t array_s_sum = to_strong_sum_array_t(s_sum);
      block_info_t block;
      block.file_NO = i;
      block.length = ret;
      block.offect = off_set;
      db[w_sum][array_s_sum].push_back(block);
      off_set += ret;
    }
    fclose(fp);
  }
  PDEBUG("DB     %d   \n",db.size());
  //delta 
  diff_job_t* job = (diff_job_t *)malloc(sizeof(diff_job_t));
  job->db = &db;
  job->delta = delta;
  
  for (int i = 0; i < delta->list_file_patch_size(); i++) {
    job->file_NO = i;
    //PDEBUG("DB     %d  \n", job->file_NO);
    PDEBUG("DB   space used =%d  file_no: %d   filename:%s\n", job->delta->SpaceUsedLong(), job->file_NO, job->delta->mutable_list_file_patch(job->file_NO)->mutable_new_file()->filename().c_str());
    delta_file2(job);
  }

  free(job);
}

void patch_opr_copy(Delta * delta, FILE *fp,char * OLD, string file_NO, int off_set, int length) {
  int file_no = atoi(file_NO.c_str());
  string file_name = insert(delta->mutable_list_file_old(file_no)->filename(), OLD);
  struct stat st;
  if (-1 != stat(file_name.c_str(), &st)) {
    FILE *fs;
    int ret = 0;
    ret = fopen_s(&fs, file_name.c_str(), "rb");
    if (ret != 0) {
      perror("patch_opr_cpy   fopen");
      PDEBUG("file  %s can't open!\n", file_name.c_str());
      exit(-1);
    }
    fseek(fs, off_set, SEEK_SET);
    unsigned char * data = (unsigned char *)malloc(length);
    ret = fread(data, 1, length, fs);
    if (ret != length) {
      PDEBUG("patch file  copy  read   error(length not match) ! \n");
      free(data);
      fclose(fs);
      fclose(fp);
      exit(-1);
    }

    ret = fwrite(data, 1, ret, fp);
    if (ret != length) {
      PDEBUG("patch file  copy  write   error(length not match) ! \n");
      free(data);
      fclose(fs);
      fclose(fp);
      exit(-1);
    }

    free(data);
    fclose(fs);
  } else {
      perror("file stat");
      PDEBUG("patch file %s ! \n", file_name.c_str());
      fclose(fp);
      exit(-1);
  }
}

  void patch_file(Delta * delta, char * OLD, char * new_file, int i) {
    FILE *fp;
    int ret = 0;
    ret = fopen_s(&fp, new_file, "wb+");
    if (ret != 0) {
      perror("fopen");
      PDEBUG("patch file  %s can't open or creat !\n", new_file);
      exit(-1);
    }
    for (int m = 0; m < delta->mutable_list_file_patch(i)->list_opr_size(); m++) {
      if (delta->mutable_list_file_patch(i)->mutable_list_opr(m)->copy()) {
        patch_opr_copy(delta, fp, OLD,delta->mutable_list_file_patch(i)->mutable_list_opr(m)->code(),
          (int)delta->mutable_list_file_patch(i)->mutable_list_opr(m)->offset(),(int) delta->mutable_list_file_patch(i)->mutable_list_opr(m)->length());
      } else {
        ret = fwrite(delta->mutable_list_file_patch(i)->mutable_list_opr(m)->code().c_str(), 1,(size_t) delta->mutable_list_file_patch(i)->mutable_list_opr(m)->length(), fp);
        if (ret != delta->mutable_list_file_patch(i)->mutable_list_opr(m)->length()) {
          PDEBUG("patch file  add  error(length not match) ! \n");
          fclose(fp);
          exit(-1);
        }
      }
    }
    fclose(fp);
  }

void patch(Delta * delta, char * OLD, char * NEW) {
  for (int i = 0; i< delta->list_file_patch_size(); i++) {
      if (ensure_path((char *)insert(delta->mutable_list_file_patch(i)->mutable_new_file()->filename(), NEW).c_str())) {
        patch_file(delta, OLD,(char *)(insert(delta->mutable_list_file_patch(i)->mutable_new_file()->filename(), NEW).c_str()),i);
      }
  }
}


bool check_old(Delta * delta, char *path) {
  printf("check old Version \n");
  bool status = true;
  for (int i = 0; i < delta->list_file_old_size(); i++) {

    status &= (delta->mutable_list_file_old(i)->blake2() == md5_file((char *)insert(delta->mutable_list_file_old(i)->filename(), path).c_str()));
    if (!status) {
      printf("file: %s   have change \n", insert(delta->mutable_list_file_old(i)->filename(), path).c_str());
      return status;
    }
  }
  if (status)
    printf("old Version match \n");
  return status;
}

bool check_new(Delta * delta, char* path, bool Debug) {
  if (Debug)
    printf("check new Version \n");
  bool status = true;
  for (int i = 0; i < delta->list_file_patch_size(); i++) {
    string s = delta->mutable_list_file_patch(i)->mutable_new_file()->filename();
    if (!Debug) {
      if (-1 == _access((char *)insert(s, path).c_str(), 0)) {
        printf("Isn't \n");
        return false;
      }
    }
    status &= (delta->mutable_list_file_patch(i)->mutable_new_file()->blake2() == md5_file((char *)(insert(delta->mutable_list_file_patch(i)->mutable_new_file()->filename(), path).c_str())));
    if (!status) {
      PDEBUG("file: %s   have change \n", insert(delta->mutable_list_file_patch(i)->mutable_new_file()->filename(), path).c_str());
      return status;
    }
  }
  if (status)
    if (Debug)
      printf("patch new Version right \n");
  return status;
}


int main()
{
  /*
  {
    std::map<strong_sum_t2, int> ttt;
    strong_sum_t2 key{};
    key.sum[0] = 1;
    ttt.insert({ key, 1 });
    ttt.insert({ key, 2 });
    key.sum[0] = 2;
    ttt.insert({ key, 3 });
    printf("%d\n", ttt.size());
  }

  {
    std::map<strong_sum_array_t, int> ttt;
    strong_sum_array_t key{1,2,3};
    ttt.insert({ key, 1});
    ttt.insert({ key, 2 });
    key[2] = 2;
    ttt.insert({ key, 3 });
    printf("%d\n", ttt.size());
  }

  system("pause");
  return 0;
  */

  printf("******* run ******\n");
    setlocale(LC_ALL, "Chinese-simplified");

    char *p = NULL;
    char exeFullPath[MAX_PATH];
    int len = GetModuleFileName(NULL, exeFullPath, MAX_PATH);
    p = strrchr(exeFullPath, '\\');
    *p = '\0';

    
  // 1 diff  0 patch
#if 1
    
    printf("**********************  diff  ***********************\n");

    len = strlen(exeFullPath);
    string path = "Settings.ini";
    printf("*********************************\n");
    //strcpy_s(exeFullPath + len,path.length(), path.c_str());
    //strcpy(exeFullPath + len, path.c_str());
    sprintf_s(exeFullPath, "%s\\%s", exeFullPath,path.c_str());
    printf("xxxx   %s\n",exeFullPath);

    char old[MAX_PATH];
    GetPrivateProfileString("Common", "OLD", "Error", old, sizeof(old), exeFullPath);

    char delta[MAX_PATH];
    GetPrivateProfileString("Common", "DELTA", "Error", delta, sizeof(delta), exeFullPath);

    char newdir[MAX_PATH];
    GetPrivateProfileString("Common", "NEW", "Error", newdir, sizeof(newdir), exeFullPath);
    printf("*********************************\n");

    Delta proto_delta;
    printf("\n************** diff *************\n");
    ReadDir(old, &proto_delta, true);
    ReadDir(newdir, &proto_delta, false);

    //remove_path(&proto_delta, old, newdir);
    
    //diff
    
    diff(&proto_delta,delta);
    
    remove_path(&proto_delta, old, newdir);
    //delta


    //save
    ofstream delta_file;
    char d_file[MAX_PATH];
    sprintf_s(d_file, "%s\\%s", delta, "delta.bin");
    ensure_path(d_file);
    delta_file.open(d_file, ios::binary);
    if (!delta_file.is_open()) {
      perror("delta.bin open");
      exit(-1);
    }
    proto_delta.SerializeToOstream(&delta_file);
    delta_file.close();

    ofstream delta_md5;
    char md5[MAX_PATH];
    sprintf_s(md5, "%s\\%s", delta, "md5.txt");
    delta_md5.open(md5, ios::binary);
    if (!delta_md5.is_open()) {
      perror("delta.bin md5 open");
      exit(-1);
    }
    delta_md5.write((md5_file(d_file)).c_str(), 32);
    delta_md5.close();
    printf("\n************** done *************\n");
    system("pause");

#else
    printf("*****  patch  ****\n");
    char d_file[MAX_PATH];
    char delta[MAX_PATH];
    char log_file[MAX_PATH];
    sprintf_s(delta, "%s\\%s", exeFullPath, "delta");
    sprintf_s(d_file, "%s\\%s", delta, "delta.bin");
    sprintf_s(log_file, "%s\\%s", exeFullPath, "patch.log");
    char md5[MAX_PATH];
    sprintf_s(md5, "%s\\%s", delta, "md5.txt");
    
    FILE* stream;
    if (freopen_s(&stream,log_file, "w", stdout) != 0)
      fprintf(stderr, "error redirecting stdout\n");

    string temp;
    fstream read_md5(md5, ios::in | ios::binary);
    if (!read_md5) {
      perror("read delta md5");
      fclose(stdout);
      exit(-1);
    }
    while (getline(read_md5, temp));
    //printf("xxx read md5.txt  %s \n",temp.c_str());
    if (md5_file(d_file) != temp) {
      perror("delta  md5  not match");
      fclose(stdout);
      exit(-1);
    }
    Delta patch_delta;
    fstream infile(d_file, ios::in | ios::binary);
    if (!infile) {
      perror("open delta file");
      fclose(stdout);
      exit(-1);
    }

    patch_delta.ParseFromIstream(&infile);
    infile.close();

    printf("Check is latest version ? \n");
    if (check_new(&patch_delta, exeFullPath, false)) {
      printf("It's the latest version \n");
      fclose(stdout);
      if (freopen_s(&stream, "CONOUT$", "w+", stdout) != 0)
        fprintf(stderr, "error re stdout\n");
      printf("done \n");
      Sleep(2000);
      exit(0);
    }

    printf("Check old  \n");
    if (!check_old(&patch_delta, exeFullPath)) {
      perror("old Version not match ");
      fclose(stdout);
      exit(-1);
    }    

    patch(&patch_delta, exeFullPath, delta);

    printf(" Check patch_new  \n");
    if (!check_new(&patch_delta, delta, true)) {
      perror("new Version not match");
      fclose(stdout);
      exit(-1);
    } else
    {
      int ret = 0;
      for (int i = 0; i < patch_delta.list_file_old_size(); i++) {
        ret = remove((char *)insert(patch_delta.mutable_list_file_old(i)->filename(), exeFullPath).c_str());
        if (ret != 0) {
          perror("remove");
          fclose(stdout);
          exit(-1);
        }
      }

      for (int i = 0; i < patch_delta.list_file_patch_size(); i++) {
        ret = rename((char *)insert(patch_delta.mutable_list_file_patch(i)->mutable_new_file()->filename(), delta).c_str(),
          (char *)insert(patch_delta.mutable_list_file_patch(i)->mutable_new_file()->filename(), exeFullPath).c_str());
        if (ret != 0) {
          perror("rename");
          fclose(stdout);
          exit(-1);
        }
      }

    }


    fclose(stdout);
    if(freopen_s(&stream,"CONOUT$", "w+", stdout)!=0)
      fprintf(stderr, "error re stdout\n");
    printf("done \n");
    Sleep(2000);
   



#endif
    return 0;
}

